package com.rankeiot.platform.service;

import cn.hutool.core.collection.ConcurrentHashSet;
import com.rankeiot.core.Current;
import com.rankeiot.core.data.UserInfo;
import com.rankeiot.core.util.StringUtil;
import org.noear.socketd.transport.core.Listener;
import org.noear.socketd.transport.core.Session;
import org.noear.socketd.transport.core.entity.StringEntity;
import org.noear.socketd.transport.core.impl.ConfigDefault;
import org.noear.socketd.transport.core.listener.EventListener;
import org.noear.socketd.transport.spring.websocket.ToSocketdWebSocketListener;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class ClientMessageService extends ToSocketdWebSocketListener {
    private final Map<String, List<SessionClient>> clientListMap = new ConcurrentHashMap<>();
    private final Map<String, SessionClient> codeClients = new ConcurrentHashMap<>();


    public ClientMessageService() {
        super(new ConfigDefault(false));
        setListener(buildListener());
    }

    private Listener buildListener() {
        return new EventListener().doOnOpen(session -> {
            String token = session.param("token");
            UserInfo user = Current.user(false, token, null, null);
            if (user == null) {
                session.close();
                return;
            }
            register(user.getUsername(), session);
            session.attrPut("user", user.getUsername());
        }).doOnMessage((session, message) -> {
            String event = message.event();
            if (event.equals("_sub")) {
                String topics = message.meta("topic");
                SessionClient client = getClient(session);
                if (client != null && topics != null) {
                    String[] topicArr = topics.split(",");
                    for (String topic : topicArr) {
                        client.subscribe(topic);
                    }
                }
            } else if (event.equals("_unsub")) {
                String topics = message.meta("topic");
                SessionClient client = getClient(session);
                if (client != null && topics != null) {
                    String[] topicArr = topics.split(",");
                    for (String topic : topicArr) {
                        client.unsubscribe(topic);
                    }
                }
            } else if (event.equals("_push")) {
                String topic = message.meta("topic");
                String to = message.meta("to");
                String msg = message.entity().dataAsString();
                if(StringUtil.isEmpty(topic)){
                    return;
                }
                if (StringUtil.isEmpty(to)) {
                    dispatch(topic, msg);
                } else {
                    sendMessage(to, topic, msg);
                }
            } else {
                //全局事件
                //方法调用
            }
        }).doOnClose(this::unregister);
    }

    private SessionClient getClient(Session session) {
        return codeClients.get(session.sessionId());
    }

    protected synchronized void register(String username, Session session) {
        boolean hasList = clientListMap.containsKey(username);
        List<SessionClient> emitterList = hasList ? clientListMap.get(username) : new ArrayList<>();
        String code = session.sessionId();
        if (!hasList) {
            clientListMap.put(username, emitterList);
        }
        SessionClient client = new SessionClient(session, username);
        codeClients.put(code, client);
        emitterList.add(client);
    }

    protected synchronized void unregister(Session session) {
        String user = session.attr("user");
        SessionClient client = codeClients.remove(session.sessionId());
        List<SessionClient> clients = clientListMap.get(user);
        if (clients != null) {
            clients.remove(client);
        }
    }

    /**
     * 向指定用户发消息,发送成功任意消息，返回true
     *
     * @param username
     * @param event
     * @param message
     */
    public boolean sendMessage(String username, String event, String message) {
        List<SessionClient> emitterList = clientListMap.get(username);
        if (emitterList != null && emitterList.size() > 0) {
            boolean flag = false;
            for (SessionClient client : emitterList) {
                try {
                    client.send(event, message);
                    flag = true;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return flag;
        }
        return false;
    }

    /**
     * 全局派发消息,发送成功任意消息，返回true，否则返回false
     */
    public boolean dispatch(String event, String message) {
        boolean flag = false;
        //获取订阅
        for (List<SessionClient> emitterList : clientListMap.values()) {
            for (SessionClient emitter : emitterList) {
                try {
                    emitter.send(event, message);
                    flag = true;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return flag;
    }


    class SessionClient {
        Session session;
        String username;
        Set<String> topic = new ConcurrentHashSet<>();

        public SessionClient(Session session, String username) {
            this.session = session;
            this.username = username;
        }

        void send(String event, String data) {
            if (topic.contains(event)) {
                try {
                    session.send(event, new StringEntity(data));
                } catch (IllegalStateException e) {
                    codeClients.remove(session.hashCode());
                    List<SessionClient> clientList = clientListMap.get(username);
                    if (clientList.size() == 1) {
                        clientListMap.remove(username);
                    } else {
                        clientList.remove(this);
                    }
                } catch (IOException e) {

                    throw new RuntimeException(e);
                }
            }
        }

        void subscribe(String event) {
            topic.add(event);
        }

        void unsubscribe(String event) {
            topic.remove(event);
        }
    }
}
